local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false)
IceHUD = LibStub("AceAddon-3.0"):NewAddon("IceHUD", "AceConsole-3.0")

local IceHUD = IceHUD

local SML = LibStub("LibSharedMedia-3.0")
local ACR = LibStub("AceConfigRegistry-3.0")
local ConfigDialog = LibStub("AceConfigDialog-3.0")
local icon = LibStub("LibDBIcon-1.0")
local AceGUI = LibStub("AceGUI-3.0")
local AceSerializer = LibStub("AceSerializer-3.0", 1)

local pendingModuleLoads = {}
local bReadyToRegisterModules = false

IceHUD.CurrTagVersion = 3
IceHUD.debugging = false

IceHUD.WowVer = select(4, GetBuildInfo())

IceHUD.validBarList = { "Bar", "HiBar", "RoundBar", "ColorBar", "RivetBar", "RivetBar2", "CleanCurves", "GlowArc", "BloodGlaives", "ArcHUD", "FangRune", "DHUD" }
IceHUD.validCustomModules = {Bar="Buff/Debuff watcher", Counter="Buff/Debuff stack counter", CD="Ability cooldown bar", Health="Health bar", Mana="Mana bar"}

--[===[@debug@
IceHUD.optionsLoaded = true
--@end-debug@]===]

local function deepcopy(object)
	local lookup_table = {}
	local function _copy(object)
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		local new_table = {}
		lookup_table[object] = new_table
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		return setmetatable(new_table, getmetatable(object))
	end
	return _copy(object)
end

IceHUD.deepcopy = deepcopy

IceHUD.Location = "Interface\\AddOns\\IceHUD"

StaticPopupDialogs["ICEHUD_CUSTOM_BAR_CREATED"] =
{
	text = L["A custom bar has been created and can be configured through Module Settings => MyCustomBar. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_COUNTER_CREATED"] =
{
	text = L["A custom counter has been created and can be configured through Module Settings => MyCustomCounter. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_CD_CREATED"] =
{
	text = L["A custom cooldown bar has been created and can be configured through Module Settings => MyCustomCD. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_HEALTH_CREATED"] =
{
	text = L["A custom health bar has been created and can be configured through Module Settings => MyCustomHealth. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_MANA_CREATED"] =
{
	text = L["A custom mana bar has been created and can be configured through Module Settings => MyCustomMana. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_DELETE_CUSTOM_MODULE"] =
{
	text = L["Are you sure you want to delete this module? This will remove all settings associated with it and cannot be un-done."],
	button1 = YES,
	button2 = NO,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
	OnAccept = function(self)
		IceHUD.IceCore:DeleteDynamicModule(self.data)
		self.data = nil
	end,
}

StaticPopupDialogs["ICEHUD_CHANGED_DOGTAG"] = {
	text = L["This option requires the UI to be reloaded. Do you wish to reload it now?"],
	button1 = YES,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
	OnAccept = function()
		ReloadUI()
	end,
	button2 = NO,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0
}

StaticPopupDialogs["ICEHUD_CHANGED_PROFILE_COMBAT"] = {
	text = L["You have changed IceHUD profiles while in combat. This can cause problems due to Blizzard's secure frame policy. You may need to reload your UI to repair IceHUD."],
	button1 = OKAY,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0
}

function IceHUD:OnInitialize()
	self:SetDebugging(false)
	self:Debug("IceHUD:OnInitialize()")

	self.IceCore = IceCore:new()
	self:RegisterPendingModules()
	self.IceCore:SetupDefaults()
	bReadyToRegisterModules = true

	self.db = LibStub("AceDB-3.0"):New("IceCoreDB", self.IceCore.defaults, true)
	if not self.db or not self.db.global or not self.db.profile then
		print(L["Error: IceHUD database not loaded correctly.  Please exit out of WoW and delete the database file (IceHUD.lua) found in: \\World of Warcraft\\WTF\\Account\\<Account Name>>\\SavedVariables\\"])
		return
	end

	self.db.RegisterCallback(self, "OnProfileShutdown", "PreProfileChanged")
	self.db.RegisterCallback(self, "OnProfileChanged", "PostProfileChanged")
	self.db.RegisterCallback(self, "OnProfileReset", "ProfileReset")
	self.db.RegisterCallback(self, "OnProfileCopied", "ProfileCopied")

	self:NotifyNewDb()

	ConfigDialog:SetDefaultSize("IceHUD", 750, 650)
	self:RegisterChatCommand("icehud", function()
			if not UnitAffectingCombat("player") then
				IceHUD:OpenConfig()
			else
				DEFAULT_CHAT_FRAME:AddMessage(L["|cff8888ffIceHUD|r: Combat lockdown restriction. Leave combat and try again."])
			end
		end)
	self:RegisterChatCommand("rl", function() ReloadUI() end)

	-- hack to allow /icehudcl to continue to function by loading the LoD options module and then re-calling the command
	--@non-debug@
	self:RegisterChatCommand("icehudcl", function(arg)
		self:UnregisterChatCommand("icehudcl")
		self:LoadOptions()
		LibStub("AceConfigCmd-3.0"):HandleCommand("icehudcl", "IceHUD", arg)
		end)
	--@end-non-debug@

	self:SyncSettingsVersions()

	self:InitLDB()
	if SML then
		SML.RegisterCallback(self, "LibSharedMedia_Registered", "UpdateMedia")
	end
end


function IceHUD:NotifyNewDb()
	self.IceCore.accountSettings = self.db.global
	self.IceCore.settings = self.db.profile
	self.IceCore:SetModuleDatabases()

	self.IceCore:CheckDisplayUpdateMessage()
end


function IceHUD:NotifyOptionsChange()
	if ACR then
		ACR:NotifyChange("IceHUD")
	end
end

function IceHUD:OnEnable(isFirst)
	self:Debug("IceHUD:OnEnable()")

	if self.db.profile.enable then
		self.IceCore:Enable()

		-- Add dual-spec support
		local LibDualSpec = LibStub('LibDualSpec-1.0', true)
		if LibDualSpec then
			LibDualSpec:EnhanceDatabase(self.db, "IceHUD")
		end
	end

	--[===[@debug@
	IceHUD_Options:OnLoad()
	--@end-debug@]===]

	if isFirst then
		self:SetDebugging(self.IceCore:GetDebug())
		self.debugFrame = ChatFrame2
	end
end

-- add settings changes/updates here so that existing users don't lose their settings
function IceHUD:SyncSettingsVersions()
	if not self.IceCore.settings.updatedOocNotFull then
		self.IceCore.settings.updatedOocNotFull = true
		self.IceCore.settings.alphaNotFull = self.IceCore.settings.alphaTarget
		self.IceCore.settings.alphaNotFullbg = self.IceCore.settings.alphaTargetbg
	end
end


function IceHUD:InitLDB()
	local LDB = LibStub and LibStub("LibDataBroker-1.1", true)

	if (LDB) then
		local ldbButton = LDB:NewDataObject("IceHUD", {
			type = "launcher",
			text = L["IceHUD"],
			label = L["IceHUD"],
			icon = "Interface\\Icons\\Spell_Frost_Frost",
			OnClick = function(button, msg)
				if not UnitAffectingCombat("player") then
					IceHUD:OpenConfig()
				else
					DEFAULT_CHAT_FRAME:AddMessage(L["|cff8888ffIceHUD|r: Combat lockdown restriction. Leave combat and try again."])
				end
			end,
		})

		if icon then
			icon:Register("IceHUD", ldbButton, self.db.profile.minimap)
		end

		if ldbButton then
			function ldbButton:OnTooltipShow()
				self:AddLine(L["IceHUD"] .. " 1.7.3.9")
				self:AddLine(L["Click to open IceHUD options."], 1, 1, 1)
			end
		end
	end
end

-- blizzard interface options
local blizOptionsPanel = CreateFrame("FRAME", "IceHUDConfigPanel", UIParent)
blizOptionsPanel.name = "IceHUD"
blizOptionsPanel.button = CreateFrame("BUTTON", "IceHUDOpenConfigButton", blizOptionsPanel, "UIPanelButtonTemplate2")
blizOptionsPanel.button:SetText("Open IceHUD configuration")
blizOptionsPanel.button:SetWidth(240)
blizOptionsPanel.button:SetHeight(30)
blizOptionsPanel.button:SetScript("OnClick", function(self) HideUIPanel(InterfaceOptionsFrame) HideUIPanel(GameMenuFrame) IceHUD:OpenConfig() end)
blizOptionsPanel.button:SetPoint('TOPLEFT', blizOptionsPanel, 'TOPLEFT', 20, -20)
InterfaceOptions_AddCategory(blizOptionsPanel)

function IceHUD:OpenConfig()
	if not ConfigDialog then return end

	if not self:LoadOptions() then
		return
	end

	if ConfigDialog.OpenFrames["IceHUD"] ~= nil then
		ConfigDialog:Close("IceHUD")
	else
		ConfigDialog:Open("IceHUD")
	end
end

function IceHUD:LoadOptions()
	if not self.optionsLoaded then
		local loaded, reason = LoadAddOn("IceHUD_Options")
		if not loaded then
			print("Failed to load options module. Reason: " .. reason)
			return false
		else
			self.optionsLoaded = true
		end
	end

	return true
end

function IceHUD:Debug(msg)
	if self.debugging then
		self.debugFrame:AddMessage(msg)
	end
end

function IceHUD:SetDebugging(bIsDebugging)
	self.debugging = bIsDebugging
end

-- rounding stuff
function IceHUD:MathRound(num, idp)
	if not num then
		return nil
	end

	local mult = 10^(idp or 0)
	return math.floor(num  * mult + 0.5) / mult
end

function IceHUD:GetBuffCount(unit, ability, onlyMine, matchByName)
	return IceHUD:GetAuraCount("HELPFUL", unit, ability, onlyMine, matchByName)
end

function IceHUD:GetDebuffCount(unit, ability, onlyMine, matchByName)
	return IceHUD:GetAuraCount("HARMFUL", unit, ability, onlyMine, matchByName)
end

function IceHUD:GetAuraCount(auraType, unit, ability, onlyMine, matchByName)
	if not unit then
		return 0
	end

	if unit == "main hand weapon" or unit == "off hand weapon" then
		local hasMainHandEnchant, mainHandExpiration, mainHandCharges, hasOffHandEnchant, offHandExpiration, offHandCharges
			= GetWeaponEnchantInfo()

		if unit == "main hand weapon" and hasMainHandEnchant then
			return mainHandCharges
		elseif unit == "off hand weapon" and hasOffHandEnchant then
			return offHandCharges
		end

		return 0
	end

	local i = 1
	local name, _, texture, applications = UnitAura(unit, i, auraType..(onlyMine and "|PLAYER" or ""))
	while name do
		if (not matchByName and string.match(texture:upper(), ability:upper()))
			or (matchByName and string.match(name:upper(), ability:upper())) then
			return applications
		end

		i = i + 1
		name, _, texture, applications = UnitAura(unit, i, auraType..(onlyMine and "|PLAYER" or ""))
	end

	return 0
end

do
	local retval = {}

	function IceHUD:HasBuffs(unit, spellIDs)
		for i=1, #spellIDs do
			retval[i] = false
		end

		local i = 1
		local name, _, texture, applications, _, _, _, _, _, _, auraID = UnitAura(unit, i)
		while name do
			for i=1, #spellIDs do
				if spellIDs[i] == auraID then
					retval[i] = applications == 0 and true or applications
					break
				end
			end

			i = i + 1
			name, _, texture, applications, _, _, _, _, _, _, auraID = UnitAura(unit, i)
		end

		return retval
	end
end

function IceHUD:OnDisable()
	IceHUD.IceCore:Disable()
end

function IceHUD:PreProfileChanged(db)
	if UnitAffectingCombat("player") then
		StaticPopup_Show("ICEHUD_CHANGED_PROFILE_COMBAT")
	end
	self.IceCore:Disable()
end

function IceHUD:PostProfileChanged(db, newProfile)
	self:NotifyNewDb()
	self.IceCore:Enable()
end

function IceHUD:ProfileReset()
	ReloadUI()
end
function IceHUD:ProfileCopied()
	ReloadUI()
end

function IceHUD:Clamp(value, min, max)
	if value < min then
		value = min
	elseif value > max then
		value = max
	elseif value ~= value or not (value >= min and value <= max) then -- check for nan...
		value = min
	end

	return value
end

function IceHUD:ShouldSwapToVehicle(...)
	local arg1, arg2 = ...

	if (arg1 == "player") then
		if (arg2) then
			return true
		end
	end

	if (UnitHasVehicleUI("player")) then
		return true
	end
end

function IceHUD:xor(val1, val2)
	return val1 and not val2 or val2 and not val1
end

function IceHUD:GetSelectValue(info, val)
	for k,v in pairs(info.option.values) do
		if v == val then
			return k
		end
	end

	return 1
end

function IceHUD:Register(element)
	assert(element, "Trying to register a nil module")
	if not bReadyToRegisterModules then
		pendingModuleLoads[#pendingModuleLoads+1] = element
	else
		self.IceCore:Register(element)
	end
end

function IceHUD:RegisterPendingModules()
	for i=1, #pendingModuleLoads do
		self.IceCore:Register(pendingModuleLoads[i])
	end
	pendingModuleLoads = {}
end

function IceHUD:UpdateMedia(event, mediatype, key)
	if not self.db.profile or not self.IceCore.enabled then
		return
	end

	if mediatype == "font" then
		if key == self.db.profile.fontFamily then
			IceHUD.IceCore:SetFontFamily(key)
		end
	elseif mediatype == "statusbar" then
		if self.TargetOfTarget and self.TargetOfTarget.moduleSettings.enabled and key == self.TargetOfTarget.moduleSettings.texture then
			self.TargetOfTarget:Redraw()
		end
	end
end

function IceHUD:CreateCustomModuleAndNotify(moduleKey, settings)
	local newMod = nil
	local popupMsg
	if moduleKey == "Bar" then -- custom bar
		newMod = IceCustomBar:new()
		popupMsg = "ICEHUD_CUSTOM_BAR_CREATED"
	elseif moduleKey == "Counter" then -- custom counter
		newMod = IceCustomCount:new()
		popupMsg = "ICEHUD_CUSTOM_COUNTER_CREATED"
	elseif moduleKey == "CD" then -- cooldown bar
		newMod = IceCustomCDBar:new()
		popupMsg = "ICEHUD_CUSTOM_CD_CREATED"
	elseif moduleKey == "Health" then -- custom health bar
		newMod = IceCustomHealth:new()
		popupMsg = "ICEHUD_CUSTOM_HEALTH_CREATED"
	elseif moduleKey == "Mana" then -- custom mana bar
		newMod = IceCustomMana:new()
		popupMsg = "ICEHUD_CUSTOM_MANA_CREATED"
	end

	if newMod ~= nil then
		IceHUD.IceCore:AddNewDynamicModule(newMod, settings)
		ConfigDialog:SelectGroup("IceHUD", "modules", newMod.elementName)
		StaticPopup_Show(popupMsg)
	end
end

local BLACKLISTED_UNIT_MENU_OPTIONS = {
	SET_FOCUS = "ICEHUD_SET_FOCUS",
	CLEAR_FOCUS = "ICEHUD_CLEAR_FOCUS",
	LOCK_FOCUS_FRAME = true,
	UNLOCK_FOCUS_FRAME = true,
}

UnitPopupButtons["ICEHUD_SET_FOCUS"] = {
	text = L["Type %s to set focus"]:format(SLASH_FOCUS1),
	tooltipText = L["Blizzard currently does not provide a proper way to right-click focus with custom unit frames."],
	dist = 0,
}

UnitPopupButtons["ICEHUD_CLEAR_FOCUS"] = {
	text = L["Type %s to clear focus"]:format(SLASH_CLEARFOCUS1),
	tooltipText = L["Blizzard currently does not provide a proper way to right-click focus with custom unit frames."],
	dist = 0,
}

local munged_unit_menus = {}
local function munge_unit_menu(menu)
	local result = munged_unit_menus[menu]
	if result then
		return result
	end

	if not UnitPopupMenus then
		munged_unit_menus[menu] = menu
		return menu
	end

	local data = UnitPopupMenus[menu]
	if not data then
		munged_unit_menus[menu] = menu
		return menu
	end

	local found = false
	for _, v in ipairs(data) do
		if BLACKLISTED_UNIT_MENU_OPTIONS[v] then
			found = true
			break
		end
	end

	if not found then
		-- nothing to remove or add, we're all fine here.
		munged_unit_menus[menu] = menu
		return menu
	end

	local new_data = {}
	for _, v in ipairs(data) do
		local blacklisted = BLACKLISTED_UNIT_MENU_OPTIONS[v]
		if not blacklisted then
			new_data[#new_data+1] = v
		elseif blacklisted ~= true then
			new_data[#new_data+1] = blacklisted
		end
	end
	local new_menu_name = "ICEHUD_" .. menu

	UnitPopupMenus[new_menu_name] = new_data
	munged_unit_menus[menu] = new_menu_name
	return new_menu_name
end
IceHUD.MungeUnitMenu = munge_unit_menu

local function figure_unit_menu(unit)
	if unit == "focus" then
		return "FOCUS"
	end

	if UnitIsUnit(unit, "player") then
		return "SELF"
	end

	if UnitIsUnit(unit, "vehicle") then
		-- NOTE: vehicle check must come before pet check for accuracy's sake because
		-- a vehicle may also be considered your pet
		return "VEHICLE"
	end

	if UnitIsUnit(unit, "pet") then
		return "PET"
	end

	if not UnitIsPlayer(unit) then
		return "TARGET"
	end

	local id = UnitInRaid(unit)
	if id then
		return "RAID_PLAYER", id
	end

	if UnitInParty(unit) then
		return "PARTY"
	end

	return "PLAYER"
end

local IceHUD_UnitFrame_DropDown = CreateFrame("Frame", "IceHUD_UnitFrame_DropDown", UIParent, "UIDropDownMenuTemplate")
UnitPopupFrames[#UnitPopupFrames+1] = "IceHUD_UnitFrame_DropDown"

IceHUD.DropdownUnit = nil
UIDropDownMenu_Initialize(IceHUD_UnitFrame_DropDown, function()
	if not IceHUD.DropdownUnit then
		return
	end

	local menu, id = figure_unit_menu(IceHUD.DropdownUnit)
	if menu then
		menu = IceHUD.MungeUnitMenu(menu)
		UnitPopup_ShowMenu(IceHUD_UnitFrame_DropDown, menu, IceHUD.DropdownUnit, nil, id)
	end
end, "MENU", nil)
